fix(textarea)!: errors following a full review of the component (#DS-5482) - #1990
fix(textarea)!: errors following a full review of the component (#DS-5482)#1990artembelik wants to merge 4 commits into
Conversation
`KbqTextarea` implements `KbqFormFieldControl`, which declares `value`, `id`, `placeholder`, `required`, `disabled`, `focused`, `empty` and `errorState` as plain members — that interface is how the form field reads them, so they stay plain accessors. The four inputs the textarea owns are signals now. `canGrow` reported `!maxRowLimitReached && bound`, so it said `false` once the textarea hit `maxRows` even though the consumer had asked for growth. The folded value drives the resize handle and is internal; `canGrow()` reports what was bound. `freeRowsHeight` used to default itself by assigning its own input in `ngOnInit`, which is why the automated migration skipped it. The fallback is a computed, so binding it later takes effect instead of being overwritten on the next init. `maxRows` and `freeRowsHeight` report `number | undefined`, which is what an unbound textarea always held. The row count is a signal, so the `kbq-textarea_max-row-limit-reached` class follows it directly. It is written inside `runOutsideAngular`, so the class used to wait for an unrelated change detection pass. The parent animation subscription is torn down with the directive, and the generated id comes from the CDK `_IdGenerator`. BREAKING CHANGE: `KbqTextarea.canGrow`, `maxRows` and `freeRowsHeight` are signal inputs, `maxRowLimitReached` is a computed, `canGrow` reports the bound value rather than folding in the row limit, and generated ids changed shape. Reported and partly rewritten by the `textarea-signals` schematic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Visit the preview URL for this PR (updated for commit 2c37404): https://koobiq-next--prs-1990-gs9ra6xb.web.app (expires Sun, 13 Sep 2026 14:24:58 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c9e37e518febda70d0317d07e8ceb35ac43c534c |
🚨 E2E tests failedReview the report for details. 💡 Comment |
Six members still carried `// TODO: Skipped for migration because:` comments from the automated tooling. They stay plain because `KbqFormFieldControl` declares them so, not because a tool gave up — the comments say that now. `disabled` and `required` coerced by hand with `coerceBooleanProperty` while the migrated inputs used `booleanAttribute`; both declare the transform on the input now, so the coercion shows up in the API report instead of hiding in a setter. `grow` was a per-instance arrow function; only the `setTimeout` needed the binding. Injections that are never reassigned are `readonly`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🚨 E2E tests failedReview the report for details. 💡 Comment |
lskramarov
left a comment
There was a problem hiding this comment.
Automated review (/code-review max) of the textarea component review. 15 findings: 10 correctness, 3 test-coverage, 1 simplification, 1 reuse — posted inline.
One of them is CI-blocking (check-public-api-any baseline), and one makes the new schematic a no-op under ng update.
Verified locally at 54f91dd: prettier, eslint --max-warnings=0 and cspell pass on every changed file, and both new suites are green (textarea.component.spec.ts 27/27, textarea-signals 10/10).
The migration wrote nothing under `ng update`: it runs a migration with no options and
migrations.json declares no schema, so `fix` arrived `undefined`. Pinned by a test that calls the
rule directly, the way `ng update` does.
Only `=` counted as a write, so `+=`, `??=`, `++`, `delete` and destructuring fell through to the
unconditional `()` append and produced TypeScript that does not parse - and the regex safety net
missed them too, because it requires `=` immediately after the member. Every assignment shape is a
write now; only `++`/`--` count among the prefix operators, since `!x.maxRowLimitReached` is spelled
the same way. `WRITABLE_MEMBERS` was empty, so the `.set(` branch it guarded and the matching
idempotency check were unreachable: both are gone.
There is a template pass now. The stated reason for skipping it was wrong - the directive declares
`exportAs: 'kbqTextarea'`, and the siblings match reference variables by exportAs on any element, not
by element name - so `{{ t.maxRows }}` and `[class.x]="t.maxRowLimitReached"` were left reading a
function object, permanently truthy, with nothing to fail compilation.
`freeRowsHeight` moves to the value-changed set. `ngOnInit` used to assign the measured line height
into the input, so an unbound read came back with a number; the fallback is internal now and the
input stays `undefined`, which turns `gap + 'px'` at a call site into a string with `undefined` in
it and no diagnostic. `grow` gets a warning of its own: it is a prototype method rather than a bound
arrow property, so `setTimeout(textarea.grow, 0)` throws where it used to work.
`optionalNumberAttribute` only excluded `null` and `undefined`, and `numberAttribute` falls back to
NaN, which is not nullish: a valueless `freeRowsHeight` ended in `coerceCssPixelValue` as `NaNpx`,
the CSSOM dropped it, and the textarea stopped growing while `rowsCount` stayed pinned at 0.
Two claims in the docs were false and are corrected in all four places each: no native resize handle
appears at the row limit (`resize: unset` follows `resize: vertical` and wins on source order), and
the generated id shape does not change for a default `APP_ID`, because the CDK omits the app id when
it is `ng` - `kbq-textarea-a1` was a TestBed artifact.
Tests: the receiver-discrimination test had no `KbqTextarea` receiver at all, so the pass returned
before reaching it; the summary assertion matched a word printed unconditionally. Both bite now, and
the row limit is reachable in a unit test - jsdom answers `line-height: normal`, which pinned
`rowsCount` at 0 and left the clamp, the class and the limit itself with no coverage.
`check-public-api-any` records textarea at 6: the three transform-carrying inputs each publish an
`unknown` that Angular derives from the transform's signature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🚨 E2E tests failedReview the report for details. 💡 Comment |
What
A full review of
textarea, in the same shape as the 20.3.0 component reviews.What stays a plain accessor, and why
KbqTextareaimplementsKbqFormFieldControl, which declaresvalue,id,placeholder,required,disabled,focused,emptyanderrorStateas plain members. That interface is how the form field reads them, and it went through its own review in 20.3.0 — so those stay accessors. What moved are the four inputs the textarea owns.canGrowdisagreed with what was boundIt reported
falseonce the textarea hitmaxRows, even though the consumer had asked for growth. The folded value is what drives the resize handle, so it became an internalgrowingcomputed;canGrow()reports what was bound. Same shape asKbqLink.tabIndexin the earlier review.freeRowsHeightdefaulted itself by writing its own inputThat is exactly why the automated migration skipped it. The fallback is a
computedover the measured line height now, so binding[freeRowsHeight]later actually takes effect instead of being overwritten the next timengOnInitruns — and it does run again, because the parent animation re-invokes it.maxRowsandfreeRowsHeightlied about being requiredBoth were declared
numberwhile an unbound textarea heldundefined.maxRowLimitReachedcomparedrowsCount > undefined, which isfalse— that accident is what made unlimited growth work. They reportnumber | undefinednow and the comparison is explicit.The row-limit class waited for someone else's change detection
rowsCountis written insiderunOutsideAngular, so[class.kbq-textarea_max-row-limit-reached]only appeared on the next unrelated change detection pass. It is a signal now and the class follows it directly.Also
parent.animationDonesubscription had no teardown; it istakeUntilDestroyed()now._IdGeneratorinstead of a module counter, so the shape changes fromkbq-textarea-1tokbq-textarea-a1.this.id = this.idin the constructor — a trick to force the setter — is replaced by initialising the backing field.Migration
textarea-signalsruns fromng update @koobiq/components@20. It rewrites the value-safe reads and reports the rest.canGrowis deliberately not rewritten: appending()would compile and hand back a different boolean at the row limit.There is no template pass —
kbqTextareais an attribute on a native<textarea>, so a reference variable is not tied to an element name the schematic can match.Documented in
docs/guides/migration.{en,ru}.md, section 18.Testing
textarea.component.spec.ts: 24 → 27 tests. New coverage for the valuelesscanGrowattribute,canGrow()reporting the bound value, and the unboundmaxRows/freeRowsHeightdefaults.textarea-signals/index.spec.ts: 10 tests.packages/components(5164 tests) andpackages/schematics(492 tests) suites pass.check-apiis in sync.The row-limit reactivity fix is not unit-testable here: jsdom reports no line height, so
rowsCountnever leaves 0.BREAKING CHANGE
🤖 Generated with Claude Code